package org.helgoboss.primary_janana_launcher;

import java.io.File;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import org.helgoboss.janana_ref_impl.*;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.awt.Window;

/**
 * Primary launcher for Janana. First entry point if you start Janana from the command line.
 */
public class PrimaryJananaLauncher {
    
	public static void main(String[] args) {
        new PrimaryJananaLauncher().launchJanana(args);
    }
    
    public void launchJanana(String[] args) {
        final JananaRefImpl janana = new JananaRefImpl();
        
        
        /* Do a clean start if requested */
        
        OptionParser parser = new OptionParser("D:");
        parser.accepts("clean");

        OptionSet options = parser.parse(args);
        
        if (options.has("clean")) {
            say("Delete bundle cache ...");
            try {
                janana.deleteBundleCache();
                say("Bundle cache succesfully deleted.");
            } catch (BundleCacheDeletionFailedException ex) {
                say("Bundle cache could not be deleted.");
                System.exit(1);
            }
        }
        
        
        /* Read Janana system properties file residing in Janana home. Useful for example for persistently setting user home directory or HTTP service port. */
        
        File jananaHome = new File(System.getProperty("user.dir"));
        janana.addToSystemProperties(new File(jananaHome, "etc/system.properties"));
        
        
        /* Read additional system properties from command line and maybe overwrite previous values. Useful for example for quickly overwriting user 
         * home directory or HTTP service port and thus instantly have a completely isolated second running Janana instance for testing purposes.
         */
        for (Object o: options.valuesOf("D")) {
            String systemPropertyDef = (String) o;
            if (systemPropertyDef.contains("=")) {
                String[] keyAndValue = systemPropertyDef.split("=");
                System.setProperty(keyAndValue[0], keyAndValue[1]);
            } else {
                System.setProperty(systemPropertyDef, "");
            }
        }
        
        
        
        /* Set system property "user.home" on base of "org.helgoboss.janana.user_home". User doesn't define "user.home" directly
         * because we want to be able to define it relatively to Janana home for USB stick mode.
         */
        String customUserHome = System.getProperty("org.helgoboss.janana.user_home");
        if (customUserHome != null) {
            File customUserHomeFile = new File(customUserHome);
            System.setProperty("user.home", customUserHomeFile.getAbsolutePath());
        }
        
        
        
        /* Set the directory which contains the initial bundles */
        
        janana.setSystemBundleLocation(new File(jananaHome, "system-bundles").toURI());
        
        
        /* Add shutdown hook in case an exit signal is sent to the JVM */
        
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                say("Begin shutting down triggered by shutdown hook");
                janana.stop();
                say("System shut down");
            }
        });
        
        
        /* Add a stop handler which force closes the system if Janana hangs on stopping */
        
        janana.setStopHandler(new Runnable() {
            public void run() {
                say("Give Janana 10 seconds to completely shutdown, then halt the JVM forcibly ...");
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException ex) {
                    throw new RuntimeException(ex);
                }
                say("Janana still didn't shutdown completely. Following user threads are still active:");
                for (Thread thread: getAllThreads()) {
                    if (!thread.isDaemon()) {
                        say("[" + thread.getId() + "] " + thread.getName() + " (priority = " + thread.getPriority() + ")");
                    }
                }
                say("Following AWT frames are still displayable:");
                for (Window window: Window.getWindows()) {
                    if (window.isDisplayable()) {
                        say(window);
                    }
                }
                say("Halt JVM forcibly ...");
                Runtime.getRuntime().halt(1);
            }
        });
        
        
        /* Set default uncaught exception handler for all threads */
        
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            public void uncaughtException(Thread t, Throwable e) {
                janana.log(1, "An exception occured in thread [" + t + "].", e);
            }
        });
        
        
        /* Start Janana */
        
        try {
            janana.start();
            say("Janana returned gracefully");
        } catch(FirstStartFailedException e) {
            /* This was Janana's first start but it failed. Stop Janana and clean up so we don't leave a mess behind and avoid forum noise. */
            e.printStackTrace();
            
            say("First start failed. Stop Janana ...");
            janana.stop();
            
            say("Janana stopped. Now delete bundle cache so we're not getting hard-to-debug errors when you run Janana next time ...");
            try {
                janana.deleteBundleCache();
                say("Bundle cache succesfully deleted.");
            } catch (BundleCacheDeletionFailedException ex) {
                say("Bundle cache could not be deleted. Please run Janana with the option '--clean' next time or delete the bundle cache directory manually.");
            }
            
            say("The first start of Janana failed. This seems to be a bug. Please report it.");
        } catch(Exception ex) {
            janana.log(4, "An exception occured which stopped janana.", ex);
            throw new RuntimeException(ex);
        }
        
	}
    
    private void say(Object obj) {
        System.out.println("[PrimaryJananaLauncher] " + obj.toString());
        System.out.flush();
    }
    
    private ThreadGroup getRootThreadGroup( ) {
        ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
        ThreadGroup parentGroup;
        while ((parentGroup = rootGroup.getParent()) != null ) {
            rootGroup = parentGroup;
        }
        return rootGroup;
    }
    
    private Thread[] getAllThreads( ) {
        final ThreadGroup root = getRootThreadGroup( );
        final ThreadMXBean thbean = ManagementFactory.getThreadMXBean( );
        int nAlloc = thbean.getThreadCount( );
        int n = 0;
        Thread[] threads;
        do {
            nAlloc *= 2;
            threads = new Thread[ nAlloc ];
            n = root.enumerate( threads, true );
        } while ( n == nAlloc );
        return java.util.Arrays.copyOf( threads, n );
    }
	
}